package core

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"time"
)

const (
	TCP_STATE_CONNECTED = iota
	TCP_STATE_DISCONNECTED
	TCP_STATE_DATA
	TCP_STATE_ERROR
)

type TcpClientCallback func(conn net.Conn, state int, err error, data []byte, datasize uint32)

type TcpClient struct {
	Conn            net.Conn
	OnClientState   TcpClientCallback
	AutoCon         bool
	AutoConnectChan chan int
}

func NewTcpClient() *TcpClient {
	return &TcpClient{AutoConnectChan: make(chan int, 100), AutoCon: false}
}
func (c *TcpClient) postReConnectEvent() {
	go func() {
		if c.AutoCon {
			time.Sleep(3 * time.Second)
			c.AutoConnectChan <- 1
		}
	}()
}

func (c *TcpClient) connectServer(address string) error {
	conn, err := net.Dial("tcp", address)
	if err != nil {
		if c.OnClientState != nil {
			c.OnClientState(nil, TCP_STATE_ERROR, err, nil, 0)
		}
		c.postReConnectEvent()
		return err
	} else {
		c.Conn = conn
		if tcpConn, ok := conn.(*net.TCPConn); ok {
			tcpConn.SetNoDelay(true)
			tcpConn.SetReadBuffer(1024 * 32)
			tcpConn.SetWriteBuffer(1024 * 32)            //视频流媒体服务 256K,IM 32K(保持10万并发)
			tcpConn.SetKeepAlive(true)                   // 保持长连接活跃
			tcpConn.SetKeepAlivePeriod(10 * time.Minute) // 减少保活探测频率
		}
	}
	return nil
}

// 释放客户端
func (c *TcpClient) Release() {
	c.AutoCon = false
	c.close()
	close(c.AutoConnectChan)
}

// 拒绝自动重新连接
func (c *TcpClient) DisableAutoConnect() {
	c.AutoCon = false
}

// 支持断开后自动重连
func (c *TcpClient) AutoConnectServer(addr string) {
	c.AutoCon = true
	go func() {
		for c.AutoCon {
			if c.connectServer(addr) == nil {
				c.LoopReceive()
			}

			data := <-c.AutoConnectChan
			if data == 0 {
				break
			}
		}
	}()
}

// 4byte+[]byte
func (c *TcpClient) writeBytes(buffer *bytes.Buffer, data []byte) ([]byte, error) {
	if err := binary.Write(buffer, binary.BigEndian, uint32(len(data))); err != nil {
		return nil, err
	}
	if _, err := buffer.Write(data); err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

// 仅发送字节数组
func (c *TcpClient) tcpSendBytes(data []byte) error {
	if c.Conn == nil {
		return nil
	}
	buf := data
	for len(buf) > 0 {
		n := 0
		var err error
		//如果是大包，则分包发送
		if len(buf) > 1460 {
			n, err = c.Conn.Write(buf[0:1460])
		} else {
			n, err = c.Conn.Write(buf)
		}

		if err != nil {
			return err
		}
		buf = buf[n:]
	}
	return nil
}

// 4byte+byte[]
func (c *TcpClient) SendBytes(data []byte) error {
	if c.Conn == nil {
		return fmt.Errorf("client not connected")
	}
	buf, err := c.writeBytes(bytes.NewBuffer(nil), data)
	if err != nil {
		return err
	}
	if err1 := c.tcpSendBytes(buf); err1 != nil {
		return err1
	}
	return nil
}

// read n bytes from conn
func (c *TcpClient) readBytes(length uint32) ([]byte, error) {
	data := bytes.NewBuffer(nil)
	slen := length
	count := 0
	for {
		buf := make([]byte, slen)
		n, err := c.Conn.Read(buf)
		//如果读取失败，则返回读取到的数据，并结果为错误
		if err != nil {
			return data.Bytes(), err
		}
		count += n
		data.Write(buf[:n])
		if uint32(count) >= length {
			break
		}
		slen -= uint32(n)
	}
	return data.Bytes(), nil
}
func (c *TcpClient) readUint32() (uint32, error) {
	data, err := c.readBytes(4)
	if err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint32(data), nil
}

// 4byte length + data
func (c *TcpClient) readPackage() ([]byte, error) {
	length, err := c.readUint32()
	if err != nil {
		return nil, err
	}
	data, err := c.readBytes(length)
	if err != nil {
		return nil, err
	}
	return data, nil
}
func (c *TcpClient) close() error {
	if c.Conn == nil {
		return fmt.Errorf("client not connected")
	}
	err := c.Conn.Close()
	c.Conn = nil
	return err
}
func (c *TcpClient) LoopReceive() error {
	defer c.close()

	if c.Conn == nil {
		return errors.New("client not connected")
	}

	if c.OnClientState != nil {
		c.OnClientState(c.Conn, TCP_STATE_CONNECTED, nil, nil, 0)
	}

	for {
		data, err := c.readPackage()
		if err != nil {
			if err == io.EOF {
				if c.OnClientState != nil {
					c.OnClientState(c.Conn, TCP_STATE_DISCONNECTED, err, data, 0)
				}
				c.postReConnectEvent()
				return err
			}
			if c.OnClientState != nil {
				c.OnClientState(c.Conn, TCP_STATE_ERROR, err, data, 0)
				c.postReConnectEvent()
				GetLog().ShowMsg(Log_Debug, "Protocol Error.")
			}
			return err
		}
		if c.OnClientState != nil {
			c.OnClientState(c.Conn, TCP_STATE_DATA, nil, data, uint32(len(data)))
		}
	}
}
